#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include "ArduinoJson.h"
#include <WebSocketsClient.h>
#include <WiFiManager.h>
#include <FastLED.h>

#define LED_PIN     D2
#define NUM_LEDS    150
#define BRIGHTNESS  64
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 100

CRGBPalette16 currentPalette;
TBlendType    currentBlending;

extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;

// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
    for( int i = 0; i < 16; ++i) {
        currentPalette[i] = CHSV( random8(), 255, random8());
    }
}

void SetupBlackAndWhiteStripedPalette()
{
    // 'black out' all 16 palette entries...
    fill_solid( currentPalette, 16, CRGB::Black);
    // and set every fourth one to white.
    currentPalette[0] = CRGB::White;
    currentPalette[4] = CRGB::White;
    currentPalette[8] = CRGB::White;
    currentPalette[12] = CRGB::White;
    
}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
    CRGB purple = CHSV( HUE_PURPLE, 255, 255);
    CRGB green  = CHSV( HUE_GREEN, 255, 255);
    CRGB black  = CRGB::Black;
    
    currentPalette = CRGBPalette16(
                                   green,  green,  black,  black,
                                   purple, purple, black,  black,
                                   green,  green,  black,  black,
                                   purple, purple, black,  black );
}

void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
    uint8_t brightness = 255;
    
    for( int i = 0; i < NUM_LEDS; ++i) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}

void ChangePalettePeriodically()
{
    uint8_t secondHand = (millis() / 1000) % 60;
    static uint8_t lastSecond = 99;
    
    if( lastSecond != secondHand) {
        lastSecond = secondHand;
        if( secondHand ==  0)  { currentPalette = RainbowColors_p;         currentBlending = LINEARBLEND; }
        if( secondHand == 10)  { currentPalette = RainbowStripeColors_p;   currentBlending = NOBLEND;  }
        if( secondHand == 15)  { currentPalette = RainbowStripeColors_p;   currentBlending = LINEARBLEND; }
        if( secondHand == 20)  { SetupPurpleAndGreenPalette();             currentBlending = LINEARBLEND; }
        if( secondHand == 25)  { SetupTotallyRandomPalette();              currentBlending = LINEARBLEND; }
        if( secondHand == 30)  { SetupBlackAndWhiteStripedPalette();       currentBlending = NOBLEND; }
        if( secondHand == 35)  { SetupBlackAndWhiteStripedPalette();       currentBlending = LINEARBLEND; }
        if( secondHand == 40)  { currentPalette = CloudColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 45)  { currentPalette = PartyColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 50)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND;  }
        if( secondHand == 55)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
    }
}

const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
    CRGB::Red,
    CRGB::Gray, // 'white' is too bright compared to red and blue
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Red,
    CRGB::Gray,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Blue,
    CRGB::Black,
    CRGB::Black
};

const char* ssid = "CMCC-kdl-2.4";
const char* password = "jinxiaohan";

const int ledPin = LED_BUILTIN;

WebSocketsClient webSocket;

struct Digital_PINDATA
{
  uint8_t dname[6] = {D1, D2, D3, D4, D5, D6};
  uint8_t d[6] = {0, 0, 0, 0, 0, 0};
  // 上一次触发时间
  unsigned long dt[6] = {0, 0, 0, 0, 0, 0};
} digital_pin_data;

struct PWM_PINDATA
{
  uint8_t dname[2] = {D7, D8};
  uint8_t d[2] = {0, 0};
  // 上一次触发时间
  unsigned long dt[2] = {0, 0};
} pwm_pin_data;

// 将数字量的针脚信息广播
void sendDigitalPinDatas()
{
  DynamicJsonDocument doc(1024);
  for (uint8_t i = 0; i < sizeof(digital_pin_data.d); i++)
  {
    doc["d" + String(i + 1)] = digital_pin_data.d[i];
  }
  String json;
  serializeJson(doc, json);
  webSocket.sendTXT(json);
}
// 将模拟量的针脚信息广播
void sendPWMPinDatas()
{
  DynamicJsonDocument doc(1024);
  for (uint8_t i = 0; i < sizeof(pwm_pin_data.d); i++)
  {
    doc["d" + String(i + 7)] = pwm_pin_data.d[i];
  }
  String json;
  serializeJson(doc, json);
  webSocket.sendTXT(json);
}

/**
   接收到指定引脚的请求后，控制输出的PWM波形
*/
void execPWMPin(uint8_t index, uint16_t value)
{
  analogWrite(pwm_pin_data.dname[index], value);
  pwm_pin_data.d[index] = value;
  sendPWMPinDatas();
}
void execDigitalPin(uint8_t index, uint16_t value)
{
  digitalWrite(digital_pin_data.dname[index], !digital_pin_data.d[index]);
  sendDigitalPinDatas();
}

void webSocketEvent(WStype_t type, uint8_t *payload, size_t length)
{
  switch (type)
  {
  case WStype_DISCONNECTED:
    Serial.printf("[WSc] Disconnected!\n");
    break;
  case WStype_CONNECTED:
  {
    Serial.printf("[WSc] Connected to url: %s\n", payload);
    webSocket.sendTXT("esp client login");
  }
  break;
  case WStype_TEXT:
  {
    Serial.printf("[WSc] get text: %s\n", payload);
    DynamicJsonDocument doc(512);
    deserializeJson(doc, (char *)payload);
    String type = doc["type"];
    if (type == "heart")
    {
      sendDigitalPinDatas();
      sendPWMPinDatas();
    }
    else if (type == "pwm")
    {

      String id = doc["id"];
      uint16_t value = doc["value"];
      if (id == "d7")
      {
        execPWMPin(0, value);
      }
      if (id == "d8")
      {
        execPWMPin(1, value);
      }
    }
    else if (type == "digital")
    {
      String id = doc["id"];
      if (id == "d1")
      {
        execDigitalPin(0, 0);
      }
      if (id == "d2")
      {
        execDigitalPin(1, 0);
      }
      if (id == "d3")
      {
        execDigitalPin(2, 0);
      }
      if (id == "d4")
      {
        execDigitalPin(3, 0);
      }
      if (id == "d5")
      {
        execDigitalPin(4, 0);
      }
      if (id == "d6")
      {
        execDigitalPin(5, 0);
      }
    }
  }
  break;
  case WStype_BIN:
    //      Serial.printf("[WSc] get binary length: %u\n", length);
    break;
  case WStype_PING:
    // pong will be send automatically
    Serial.printf("[WSc] get ping\n");
    break;
  case WStype_PONG:
    // answer to a ping we send
    Serial.printf("[WSc] get pong\n");
    break;
  }
}

void setup() {

  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.println("\n Configure WIFI...");
  
  WiFiManager wifiManager;
  
  wifiManager.autoConnect("ESP8266-LED-AP");
  Serial.println("connected...yeey :)");
  Serial.println(WiFi.localIP());
  // 设置默认的电平
  for (uint8_t i = 0; i < sizeof(digital_pin_data.dname); i++)
  {
    pinMode(digital_pin_data.dname[i], OUTPUT);
  }
  for (uint8_t i = 0; i < sizeof(pwm_pin_data.dname); i++)
  {
    pinMode(pwm_pin_data.dname[i], OUTPUT);
  }
  digitalWrite(ledPin, HIGH);

  // server address, port and URL
  webSocket.begin("101.43.241.226", 5765, "/?id=kdl&key=76098491");
  webSocket.onEvent(webSocketEvent);
  webSocket.setReconnectInterval(5000);
  webSocket.enableHeartbeat(15000, 3000, 2);

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
    FastLED.setBrightness(  BRIGHTNESS );
    
    currentPalette = RainbowColors_p;
    currentBlending = LINEARBLEND;
}

void loop() {
  webSocket.loop();

  unsigned long currenttime = millis();
  // 检查引脚的状态
  bool is_changed = false;
  for (int8 i = 0; i < sizeof(digital_pin_data.dname); i++)
  {
    int8_t tmp = digitalRead(digital_pin_data.dname[i]);
    if (digital_pin_data.d[i] != tmp)
    {
      // 防止误触发（<50毫秒的信号将忽略）
      if (currenttime - digital_pin_data.dt[i] > 50)
      {
        digital_pin_data.dt[i] = currenttime;
        is_changed = true;
        digital_pin_data.d[i] = tmp;
      }
    }
  }
  // 如果引脚数据发生变化，就发送给所有客户端
  if (is_changed)
  {
    Serial.println("Pin has changed");
    sendDigitalPinDatas();
  }

  ChangePalettePeriodically();
    
    static uint8_t startIndex = 0;
    startIndex = startIndex + 1; /* motion speed */
    
    FillLEDsFromPaletteColors( startIndex);
    
    FastLED.show();
    FastLED.delay(1000 / UPDATES_PER_SECOND);
}